#define _XOPEN_SOURCE 500
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/epoll.h>
#include <netinet/in.h>
#include <pwd.h>
#include <pthread.h>
#include <errno.h>
#include <shadow.h>
#include <strings.h>
#include <string.h>
#include "public.h"
#include "ipmsg.h"
#include "userdb.h"
#include "user_proc.h"
#include "socketpair_protocol.h"
#include "udp.h"

#define MAX_LISTEN 128
#define MAX_EPOLL_SIZE 1024
#define MAX_TIMEOUT 10000	//epoll_wait timeout 10s

lu_desc_t *local_user_desc;	//本地用户列表描述符
int udp_sockfd;

static int epoll_common_fd;	//非TCP传文件的epoll

static int init_un(void);
static int init_udp(void);
static int init_epoll(int udp_sockfd, int un_sockfd);
static int ipmsg_login(uid_t uid, const char *passwd);
static void *login_thread(void *arg);
static int init_sig(void);

int main(int argc, char **argv)
{
	int un_sockfd;
	struct epoll_event events[MAX_EPOLL_SIZE];
	int epoll_handle;
	int nfds, i;
	struct sockaddr_un cliaddr;
	socklen_t clilen = sizeof cliaddr;
	int conn_un_fd;
	pthread_t tid;
	char proc_buf[PACKET_LEN_MAX];
	char udp_buf[UDP_PACKET_LEN_MAX];

	if (init_sig() < 0) {
		goto end;
	}
	udp_sockfd = init_udp();
	un_sockfd = init_un();
	if (udp_sockfd < 0 || un_sockfd < 0) {
		goto end;
	}
	epoll_handle = init_epoll(udp_sockfd, un_sockfd);
	if (epoll_handle < 0) {
		goto end;
	}
	epoll_common_fd = epoll_handle;
	local_user_desc = local_user_open();
	if (!local_user_desc)
		goto end;

	while (1) {
		nfds = epoll_wait(epoll_handle, events, MAX_EPOLL_SIZE, MAX_TIMEOUT);
		if (nfds < 0) {
			if (errno != EINTR) {
				perror("epoll_wait");
				goto end;
			}
		}
		for (i = 0; i < nfds; i++) {
			if (events[i].data.fd == un_sockfd) {	//用户登录连接到来
				while (1) {
					conn_un_fd = accept(un_sockfd, (struct sockaddr *)&cliaddr, &clilen);
					if (conn_un_fd < 0) {
						if (errno == EAGAIN) {
							break;
						}
						continue;
					}
					pthread_create(&tid, NULL, login_thread, (void *)conn_un_fd);
				}
			}
			else if (events[i].data.fd == udp_sockfd) {	//收到网络中的udp数据
				while (1) {
					if (read(udp_sockfd, udp_buf, sizeof udp_buf) < 0)
						break;
					udp_protocol(udp_sockfd, udp_buf);
				}
			}
			else {	//收到为client服务的进程发来的数据
				struct local_userdb *p = NULL;
				p = local_user_search(local_user_desc, sockfd, &(events[i].data.fd), sizeof(events[i].data.fd));
				if (!p) {
					continue;
				}
				while (1) {
					if (read(p->sockfd, proc_buf, sizeof proc_buf) < 0) {
						if (errno == EAGAIN) {
							break;
						}
						else break;
					}
					proc_protocol(p->sockfd, proc_buf);
				}
			}
		}
	}

end:
	if (local_user_desc)
		local_user_close(local_user_desc);
	return 0;
}

static void *login_thread(void *arg)
{
	int sockfd = (int)arg;
	char buffer[PACKET_LEN_MAX];
	char *ptr = buffer;
	char *login_name = buffer;
	uid_t uid;
	gid_t gid;
	pid_t pid;
	char *passwd = NULL;
	struct local_userdb *user = NULL, *tmp_user = NULL;
	int sv[2];
	struct epoll_event ev;

	pthread_detach(pthread_self());
	memset(buffer, 0, sizeof buffer);
	if (read(sockfd, buffer, sizeof buffer - 1) < 0) {
		perror("read");
		goto end;
	}
	ptr = strchr(ptr, ' ');
	if (ptr == NULL) {
		goto end;
	}
	*ptr = '\0';
	ptr++;
	if (sscanf(ptr, "%d", &uid) != 1) {
		goto end;
	}
	ptr = strchr(ptr, ' ');
	if (ptr == NULL) {
		goto end;
	}
	ptr++;
	if (sscanf(ptr, "%d", &gid) != 1) {
		goto end;
	}
	ptr = strchr(ptr, ' ');
	if (ptr == NULL) {
		goto end;
	}
	passwd = ptr + 1;
	if (ipmsg_login(uid, passwd) < 0) {
		goto end;
	}
	tmp_user = local_user_search(local_user_desc, uid, &uid, sizeof(uid));
	if (tmp_user) {
		goto end;
	}
	/* 向local_userdb中添加用户,fork,setuid等操作 */
	if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, sv) < 0) {
		goto end;
	}
	pid = fork();
	if (pid < 0) {
		goto end;
	}
	if (pid == 0) {
		if (setuid(uid) < 0) {
			exit(1);
		}
		close(sv[0]);
		user_proc(sockfd, sv[1]);
		exit(0);
	}
	close(sockfd);
	close(sv[1]);
	if (fcntl(sv[0], F_SETFL, fcntl(sv[0], F_GETFL, 0) | O_NONBLOCK) < 0) {
		perror("fcntl");
		goto end;
	}
	ev.events = EPOLLIN | EPOLLET;
	ev.data.fd = sv[0];
	if (epoll_ctl(epoll_common_fd, EPOLL_CTL_ADD, sv[0], &ev) < 0) {
		perror("epoll_ctl");
		goto end;
	}
	user = create_local_user(uid, gid, login_name, sv[0], pid);
	local_user_add(local_user_desc, user);
end:
	return NULL;
}

static int ipmsg_login(uid_t uid, const char *passwd)
{
	char *encrypted = NULL, *correct = NULL;
	struct passwd pw, *pwbufp = NULL;
	char buffer[1024];
	struct spwd *sp = NULL;
	int ret = -1;

	ret = getpwuid_r(uid, &pw, buffer, sizeof buffer, &pwbufp);
	if (ret != 0) {
		ret = -1;
		goto end;
	}
	if (pwbufp == NULL) {
		ret = -1;
		goto end;
	}
	sp = getspnam(pw.pw_name);
	if (sp == NULL) {
		goto end;
	}
	correct = sp->sp_pwdp;
	encrypted = crypt (passwd, correct);
	if (strcmp(encrypted, correct)) {
		ret = -1;
		goto end;
	}
	ret = 0;
end:
	return ret;
}

static int init_epoll(int udp_sockfd, int un_sockfd)
{
	int epoll_handle;
	int ret = -1;
	struct epoll_event ev;

	epoll_handle = epoll_create(MAX_EPOLL_SIZE);
	if (epoll_handle < 0) {
		perror("epoll_create");
		goto end;
	}
	ev.events = EPOLLIN | EPOLLET;
	ev.data.fd = udp_sockfd;
	if (epoll_ctl(epoll_handle, EPOLL_CTL_ADD, udp_sockfd, &ev) < 0) {
		perror("epoll_ctl");
		goto end;
	}
	ev.events = EPOLLIN | EPOLLET;
	ev.data.fd = un_sockfd;
	if (epoll_ctl(epoll_handle, EPOLL_CTL_ADD, un_sockfd, &ev) < 0) {
		perror("epoll_ctl");
		goto end;
	}
	ret = epoll_handle;
end:
	return ret;
}

static int init_un(void)	/* 初始化监听登录的本地套接字 */
{
	int un_sockfd;
	struct sockaddr_un serv;
	int ret = -1;

	un_sockfd = socket(PF_UNIX, SOCK_SEQPACKET, 0);
	if (un_sockfd < 0) {
		perror("socket");
		goto end;
	}
	unlink(UNIX_PATH);
	bzero(&serv, sizeof serv);
	serv.sun_family = AF_UNIX;
	strcpy(serv.sun_path, UNIX_PATH);
	if (bind(un_sockfd, (struct sockaddr *)&serv, sizeof serv) < 0) {
		perror("bind");
		goto end;
	}
	if (listen(un_sockfd, MAX_LISTEN) < 0) {
		perror("listen");
		goto end;
	}
	if (fcntl(un_sockfd, F_SETFL, fcntl(un_sockfd, F_GETFL, 0) | O_NONBLOCK) < 0) {
		perror("fcntl");
		goto end;
	}
	ret = un_sockfd;
end:
	return ret;
}

static int init_udp(void)
{
	int ret = -1, optval = 1;
	int udp_sockfd;
	struct sockaddr_in me;

	udp_sockfd = socket(AF_INET, SOCK_DGRAM, 0);
	if (udp_sockfd < 0) {
		perror("socket");
		goto end;
	}
	bzero(&me, sizeof me);
	me.sin_family = AF_INET;
	me.sin_port = htons(IPMSG_PORT);
	me.sin_addr.s_addr = INADDR_ANY;

	if (setsockopt(udp_sockfd, SOL_SOCKET, SO_BROADCAST, &optval, sizeof optval) < 0) {
		perror("setsockopt");
		goto end;
	}
	
	if (bind(udp_sockfd, (struct sockaddr *)&me, sizeof me) < 0) {
		perror("bind");
		goto end;
	}
	if (fcntl(udp_sockfd, F_SETFL, fcntl(udp_sockfd, F_GETFL, 0) | O_NONBLOCK) < 0) {
		perror("fcntl");
		goto end;
	}
	ret = udp_sockfd;
end:
	return ret;
}

static void child_exit_sig(int signo)
{
	int status;
	pid_t pid;
	if (signo == SIGCHLD) {
		while (1) {
			pid = waitpid(-1, &status, WNOHANG);
			if (pid <= 0)
				break;
		}
	}
}

static int init_sig(void)
{
	struct sigaction sigact;
	int ret = -1;

	sigact.sa_handler = child_exit_sig;
	sigemptyset(&sigact.sa_mask);
	sigact.sa_flags = SA_RESTART;
	if (sigaction(SIGCHLD, &sigact, NULL) < 0) {
		goto end;
	}
	ret = 0;
end:
	return ret;
}
